import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
Visualisation des Données de Sécurité ANSSI¶
Ce notebook analyse et visualise les données consolidées des bulletins de sécurité ANSSI et les CVEs enrichies.
df = pd.read_csv(r'files/consolidated_data_all.csv')
# Charger les données enrichies des CVEs
df_enriched = pd.read_csv(r'files/enriched_cves_all.csv')
print("Données enrichies chargées.")
Données enrichies chargées.
# Nettoyer les données enrichies
df_enriched['cvss_score'] = pd.to_numeric(df_enriched['cvss_score'], errors='coerce')
df_enriched['epss_score'] = pd.to_numeric(df_enriched['epss_score'], errors='coerce')
print("Données enrichies nettoyées.")
Données enrichies nettoyées.
# Histogramme des scores CVSS enrichis
plt.figure(figsize=(10, 6))
plt.hist(df_enriched['cvss_score'].dropna(), bins=10, edgecolor='black')
plt.title('Distribution des Scores CVSS (Enrichis)')
plt.xlabel('Score CVSS')
plt.ylabel('Fréquence')
plt.show()
Montre combien de vulnérabilités ont tel niveau de gravité. Si le pic est vers la droite, cela signifie beaucoup de vulnérabilités graves à prioriser.
# Nettoyage des données
df['Date de publication'] = pd.to_datetime(df['Date de publication'], errors='coerce')
df['Score CVSS'] = pd.to_numeric(df['Score CVSS'], errors='coerce')
df['Score EPSS'] = pd.to_numeric(df['Score EPSS'], errors='coerce')
print("Données nettoyées.")
Données nettoyées.
# Histogramme des scores CVSS (avec Matplotlib)
plt.figure(figsize=(10, 6))
plt.hist(df['Score CVSS'].dropna(), bins=10, edgecolor='black')
plt.title('Distribution des Scores CVSS')
plt.xlabel('Score CVSS')
plt.ylabel('Fréquence')
plt.show()
Même idée que ci‑dessus mais pour toutes les données. Comparez-le avec l'enrichi : s'ils diffèrent, l'échantillon enrichi n'est pas identique au global.
# Diagramme circulaire des types CWE (top 10) avec Plotly (inclut 'Non disponible')
if 'Type CWE' in df.columns:
# Remplacer les NaN par 'Non disponible' puis compter; inclut 'Non disponible' dans le top
cwe_counts = df['Type CWE'].fillna('Non disponible').value_counts().head(10)
fig = px.pie(values=cwe_counts.values, names=cwe_counts.index, title="Top 10 Types de CWE (inclut 'Non disponible')")
fig.show()
else:
print("Colonne 'Type CWE' manquante dans le dataframe.")
Affiche quelles familles d'erreurs reviennent le plus. Une grosse part signifie une cause fréquente; "Non disponible" indique des cas sans type identifié.
# Nuage de points : Score CVSS vs Score EPSS
df_scatter = df.dropna(subset=['Score CVSS', 'Score EPSS'])
fig = px.scatter(df_scatter, x='Score CVSS', y='Score EPSS', title='Corrélation CVSS vs EPSS')
fig.show()
Chaque point montre gravité (CVSS) et probabilité d'exploitation (EPSS). Si les points sont dispersés sans tendance, la gravité ne prédit pas forcément l'exploitation réelle.
# Courbe cumulative des vulnérabilités par date
df_sorted = df.sort_values('Date de publication')
df_cum = df_sorted.groupby('Date de publication').size().cumsum()
fig = px.line(x=df_cum.index, y=df_cum.values, title='Évolution Cumulative des Vulnérabilités')
fig.show()
Compte total cumulé dans le temps. Une montée rapide récente signale une période d'activité accrue (plus de vulnérabilités publiées).
# Boxplot des scores CVSS par type de bulletin
fig = px.box(df, x='Type de bulletin', y='Score CVSS', title='Scores CVSS par Type de Bulletin')
fig.show()
Compare la gravité typique et la variabilité entre types de bulletin. Un type avec médiane élevée ou beaucoup d'« outliers » mérite plus d'attention.
# Heatmap de corrélation
numeric_df = df[['Score CVSS', 'Score EPSS']].dropna()
if not numeric_df.empty:
corr = numeric_df.corr()
fig = px.imshow(corr, text_auto=True, title='Heatmap de Corrélation CVSS/EPSS')
fig.show()
else:
print("Pas assez de données numériques pour la heatmap.")
Indique si CVSS et EPSS évoluent ensemble. Une valeur proche de 1 signifie forte relation; proche de 0 signifie peu de lien.
# Vulnérabilités par année
if 'Date de publication' not in df.columns:
print('Colonne Date de publication manquante')
else:
df['year'] = df['Date de publication'].dt.year
year_counts = df.groupby('year').size()
fig = px.bar(x=year_counts.index, y=year_counts.values, labels={'x':'Année','y':'Nombre de vulnérabilités'}, title='Vulnérabilités par année')
fig.show()
Montre le nombre par année. Permet voir si le nombre augmente, diminue ou reste stable au fil des ans.
# Graphe: Répartition des niveaux CVSS (données enrichies)
try:
# Vérifier que df_enriched existe et contient cvss_score
if 'df_enriched' in globals() and 'cvss_score' in df_enriched.columns:
bins = [-0.01, 3, 6, 8, 10]
labels = ['Faible', 'Moyenne', 'Élevée', 'Critique']
df_enriched['cvss_level'] = pd.cut(df_enriched['cvss_score'], bins=bins, labels=labels, ordered=True)
counts = df_enriched['cvss_level'].value_counts(sort=False).reindex(labels[::-1]).fillna(0)
fig = px.bar(x=counts.index, y=counts.values, labels={'x':'Niveau CVSS','y':'Nombre'}, title='Répartition des niveaux CVSS (enrichis)')
fig.show()
else:
print("df_enriched introuvable ou champ 'cvss_score' manquant. Chargez d'abord les données enrichies.")
except Exception as e:
print('Erreur lors de la génération du graphe CVSS :', type(e).__name__, e)
Montre la part de vulnérabilités Faible/Moyenne/Élevée/Critique. Beaucoup de « Critique » → agir en priorité.
print("Visualisations générées")
Visualisations générées